import jsonSchemaGenerator from 'json-schema-generator';
import { isUndefined, cloneDeep, isPlainObject } from 'vtils';
import Mock from 'mockjs';
import path from 'path';
import { castArray, forOwn, isArray, isEmpty, isObject, dedent } from 'vtils';
import randomString from 'random-string';
import { compile, Options } from 'json-schema-to-typescript';
// import { FileData } from '../helpers';
import type { JSONSchema4, JSONSchema4TypeName } from 'json-schema';
import { PropDefinitions } from '../types';
import prettier from 'prettier';

import sleep from './sleep';
import promiseIterator from './promise-iterator';
import apiPathParse from './api-path-parse';
import getModules from './get-modules';

export { sleep, promiseIterator, apiPathParse, getModules };
/**
 * 将路径统一为 unix 风格的路径。
 *
 * @param path 路径
 * @returns unix 风格的路径
 */
export function toUnixPath(path: string) {
  return path.replace(/[/\\]+/g, '/');
}

/**
 * 获得规范化的相对路径。
 *
 * @param from 来源路径
 * @param to 去向路径
 * @returns 相对路径
 */
export function getNormalizedRelativePath(from: string, to: string) {
  return toUnixPath(path.relative(path.dirname(from), to))
    .replace(/^(?=[^.])/, './')
    .replace(/\.(ts|js)x?$/i, '');
}

/**
 * 原地处理 JSONSchema。
 *
 * @param jsonSchema 待处理的 JSONSchema
 * @returns 处理后的 JSONSchema
 */
export function processJsonSchema<T extends JSONSchema4>(jsonSchema: T): T {
  /* istanbul ignore if */
  if (!isObject(jsonSchema)) return jsonSchema;

  // 去除 title 和 id，防止 json-schema-to-typescript 提取它们作为接口名
  delete jsonSchema.title;
  delete jsonSchema.id;

  // 忽略数组长度限制
  delete jsonSchema.minItems;
  delete jsonSchema.maxItems;

  // 将 additionalProperties 设为 false
  jsonSchema.additionalProperties = false;

  // Mock.toJSONSchema 产生的 properties 为数组，然而 JSONSchema4 的 properties 为对象
  if (isArray(jsonSchema.properties)) {
    jsonSchema.properties = (jsonSchema.properties as JSONSchema4[]).reduce<
      Exclude<JSONSchema4['properties'], undefined>
    >((props, js) => {
      props[js.name] = js;
      return props;
    }, {});
  }

  // 继续处理对象的子元素
  if (jsonSchema.properties) {
    forOwn(jsonSchema.properties, processJsonSchema);
  }

  // 继续处理数组的子元素
  if (jsonSchema.items) {
    castArray(jsonSchema.items).forEach(processJsonSchema);
  }

  return jsonSchema;
}

/**
 * 将 JSONSchema 字符串转为 JSONSchema 对象。
 *
 * @param str 要转换的 JSONSchema 字符串
 * @returns 转换后的 JSONSchema 对象
 */
export function jsonSchemaStringToJsonSchema(str: string): JSONSchema4 {
  return processJsonSchema(JSON.parse(str));
}

/**
 * 获得 JSON 数据的 JSONSchema 对象。
 *
 * @param json JSON 数据
 * @returns JSONSchema 对象
 */
export function jsonToJsonSchema(json: any): JSONSchema4 {
  return processJsonSchema(jsonSchemaGenerator(json));
}

/**
 * 获得 mockjs 模板的 JSONSchema 对象。
 *
 * @param template mockjs 模板
 * @returns JSONSchema 对象
 */
export function mockjsTemplateToJsonSchema(template: any): JSONSchema4 {
  return processJsonSchema(Mock.toJSONSchema(template) as any);
}

/**
 * 获得属性定义列表的 JSONSchema 对象。
 *
 * @param propDefinitions 属性定义列表
 * @returns JSONSchema 对象
 */
export function propDefinitionsToJsonSchema(propDefinitions: PropDefinitions): JSONSchema4 {
  return processJsonSchema({
    type: 'object',
    required: propDefinitions.reduce<string[]>((res, prop) => {
      if (prop.required) {
        res.push(prop.name);
      }
      return res;
    }, []),
    properties: propDefinitions.reduce<Exclude<JSONSchema4['properties'], undefined>>(
      (res, prop) => {
        res[prop.name] = {
          type: prop.type,
          description: prop.comment,
          // tsType 为 file interface 的 key
          // 改用Blob作为文件类型
          ...(prop.type === ('file' as any) ? { tsType: 'Blob' } : {})
        };
        return res;
      },
      {}
    )
  });
}

const JSTTOptions: Partial<Options> = {
  bannerComment: '',
  style: {
    bracketSpacing: false,
    printWidth: 100,
    semi: true,
    singleQuote: true,
    tabWidth: 2,
    trailingComma: 'none',
    useTabs: false
  }
};

/**
 * 根据 JSONSchema 对象生产 TypeScript 类型定义。
 *
 * @param jsonSchema JSONSchema 对象
 * @param typeName 类型名称
 * @returns TypeScript 类型定义
 */
export async function jsonSchemaToType(jsonSchema: JSONSchema4, typeName: string): Promise<string> {
  if (isEmpty(jsonSchema)) {
    return `export interface ${typeName} {}`;
  }
  // JSTT 会转换 typeName，因此传入一个全大写的假 typeName，生成代码后再替换回真正的 typeName
  const fakeTypeName = `FAKE${randomString()}`.toUpperCase();

  const code = await compile(jsonSchema, fakeTypeName, JSTTOptions);
  return code.replace(fakeTypeName, typeName).trim();
}

/**
 * 代码格式化
 * @param code 代码字符串
 * @param opts prettier配置
 */
export function codeFormat(code: string, opts?: prettier.Options) {
  const _opts = opts || JSTTOptions.style;
  return prettier.format(dedent`${code}`, _opts);
}

export function transformSchemaNumber(_properties: Record<string, JSONSchema4> | JSONSchema4) {
  const properties = cloneDeep(_properties);
  const checkIsInteger = (typeName: JSONSchema4TypeName | JSONSchema4TypeName[]) => {
    const numberType = ['number', 'integer', 'any'];
    const _typeName = castArray(typeName);

    return _typeName.some((_it) => {
      return numberType.includes(_it);
    });
  };

  Object.keys(properties).forEach((key) => {
    const _properties = properties[key];
    const protectKeys = ['pageNum', 'pageSize', 'count', 'total', 'page'];

    if (!isUndefined(_properties.default) && _properties.type) {
      // 处理 `type` 和 `default` 不一致的情况
      if (checkIsInteger(_properties.type) && typeof _properties.default !== 'number') {
        _properties.default = Number(_properties.default);
      }
    }

    const transformUnionType = (schema: JSONSchema4, keyName: string) => {
      type TProperties = JSONSchema4['properties'];
      const handleProperties = (properties?: TProperties | TProperties[]) => {
        if (!properties) return;
        if (isObject(properties)) {
          Object.keys(properties || {}).forEach((_key) => {
            transformUnionType(properties![_key], _key);
          });
        } else if (Array.isArray(properties)) {
          (properties as TProperties[]).forEach((item) => {
            forOwn(item, handleProperties);
          });
        }
      };
      if (schema.type && !protectKeys.includes(keyName)) {
        if (checkIsInteger(schema.type)) {
          schema.type = isArray(schema.type)
            ? Array.from(new Set([...schema.type, 'string']))
            : [schema.type, 'string'];
        } else if (schema.type === 'array') {
          if (schema.items) {
            if (Array.isArray(schema.items)) {
              schema.items.forEach((item) => {
                handleProperties(item.properties);
              });
            } else {
              handleProperties(schema.items.properties);
            }
          }
        } else if (schema.type === 'object') {
          Object.keys(schema.properties || {}).forEach((_key) => {
            transformUnionType(schema.properties![_key], _key);
          });
        }
      }
    };

    transformUnionType(_properties, key);
  });

  if (properties.properties && properties.type === 'object') {
    properties.properties = transformSchemaNumber(properties.properties);
  }
  return properties;
}
